package plus.lgx.textsearcher.searcher.printer;

import plus.lgx.textsearcher.entity.PrintData;

import java.io.*;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Map;

import static java.util.Map.entry;
import static plus.lgx.textsearcher.constant.Const.LS;
import static plus.lgx.textsearcher.constant.Const.MAX_TEMPLATE_FILE_LENGTH;

/**
 * Class name: OutputHandler
 *
 * Create time: 12/21/22 9:11 AM
 *
 * @author lgx
 * @version 1.0
 */
public class TemplateOutputHandler extends AbstractOutputHandler {

    /*
    $ITEM$
    $FILENAME$
    $FILEPATH$
    $FILESIZE$
    $ITEMNUMBER$
    $FILENUMBER$
    $SEARCH_TEXT$
    $OUTPUT_FILENAME$
    $OUTPUT_FILEPATH$
    $TOTAL_FILENUMBER$
    $TOTAL_ITEMNUMBER$


    ##HEAD
    ###


    ##FILE HEAD

    $TOTAL_FILENUMBER$.$FILENUMBER$. 【$FILENAME$】 $FILEPATH$  $FILESIZE$
    ----------
    ###


    ##ITEM
      $TOTAL_ITEMNUMBER$.$ITEMNUMBER$. $ITEM$
    ###


    ##FILE TAIL

    ###


    ##TAIL

    TOTAL: $TOTAL_ITEMNUMBER$ matched text(s) in $FILENUMBER$ file(s).
    ###
     */

    private String headTemplate = "";
    private String itemTemplate = "";
    private String tailTemplate = "";
    private String file1Template = "";
    private String file2Template = "";

    private final List<Field> HEAD = new ArrayList<>();
    private final List<Field> ITEM = new ArrayList<>();
    private final List<Field> TAIL = new ArrayList<>();
    private final List<Field> FILE_HEAD = new ArrayList<>();
    private final List<Field> FILE_TAIL = new ArrayList<>();

    private static final Map<String, String> VARIABLES = Map.ofEntries(
            entry("ITEM", "%s"),
            entry("FILENAME", "%s"),
            entry("FILEPATH", "%s"),
            entry("FILESIZE", "%s"),
            entry("ITEMNUMBER", "%d"),
            entry("FILENUMBER", "%d"),
            entry("SEARCH_TEXT", "%s"),
            entry("OUTPUT_FILENAME", "%s"),
            entry("OUTPUT_FILEPATH", "%s"),
            entry("TOTAL_FILENUMBER", "%d"),
            entry("TOTAL_ITEMNUMBER", "%d")
    );

    private static final Map<String, Field> VAR_FIELDS;

    static {
        Map<String, Field> map;
        try {
            map = Map.ofEntries(
                    entry("ITEM", PrintData.class.getField("item")),
                    entry("FILENAME", PrintData.class.getField("fileName")),
                    entry("FILEPATH", PrintData.class.getField("filePath")),
                    entry("FILESIZE", PrintData.class.getField("fileSize")),
                    entry("FILENUMBER", PrintData.class.getField("fileNumber")),
                    entry("ITEMNUMBER", PrintData.class.getField("itemNumber")),
                    entry("SEARCH_TEXT", PrintData.class.getField("searchText")),
                    entry("OUTPUT_FILENAME", PrintData.class.getField("outputFileName")),
                    entry("OUTPUT_FILEPATH", PrintData.class.getField("outputFilePath")),
                    entry("TOTAL_FILENUMBER", PrintData.class.getField("totalFileNumber")),
                    entry("TOTAL_ITEMNUMBER", PrintData.class.getField("totalItemNumber"))
            );
        } catch (NoSuchFieldException ignored) {
            map = null;
        }
        VAR_FIELDS = map;
    }

    private enum Scope {
        END("###"),
        TAIL_SCOPE("##TAIL"),
        HEAD_SCOPE("##HEAD"),
        ITEM_SCOPE("##ITEM"),
        FILE_TAIL_SCOPE("##FILE TAIL"),
        FILE_HEAD_SCOPE("##FILE HEAD");
        final String val;
        Scope(String val) {
            this.val = val;
        }
        void setTemplate(String s, TemplateOutputHandler h) {
            switch (this) {
                case HEAD_SCOPE -> h.headTemplate = s;
                case TAIL_SCOPE -> h.tailTemplate = s;
                case ITEM_SCOPE -> h.itemTemplate = s;
                case FILE_HEAD_SCOPE -> h.file1Template = s;
                case FILE_TAIL_SCOPE -> h.file2Template = s;
            }
        }
        void append(String var, TemplateOutputHandler h) {
            switch (this) {
                case HEAD_SCOPE -> h.HEAD.add(VAR_FIELDS.get(var));
                case ITEM_SCOPE -> h.ITEM.add(VAR_FIELDS.get(var));
                case TAIL_SCOPE -> h.TAIL.add(VAR_FIELDS.get(var));
                case FILE_HEAD_SCOPE -> h.FILE_HEAD.add(VAR_FIELDS.get(var));
                case FILE_TAIL_SCOPE -> h.FILE_TAIL.add(VAR_FIELDS.get(var));
            }
        }
        boolean clear(TemplateOutputHandler h) {
            switch (this) {
                case HEAD_SCOPE -> {
                    boolean ret = h.HEAD.size() > 0;
                    h.HEAD.clear();
                    return ret;
                }
                case ITEM_SCOPE -> {
                    boolean ret = h.ITEM.size() > 0;
                    h.ITEM.clear();
                    return ret;
                }
                case TAIL_SCOPE -> {
                    boolean ret = h.TAIL.size() > 0;
                    h.TAIL.clear();
                    return ret;
                }
                case FILE_HEAD_SCOPE -> {
                    boolean ret = h.FILE_HEAD.size() > 0;
                    h.FILE_HEAD.clear();
                    return ret;
                }
                case FILE_TAIL_SCOPE -> {
                    boolean ret = h.FILE_TAIL.size() > 0;
                    h.FILE_TAIL.clear();
                    return ret;
                }
                default -> {
                    return false;
                }
            }
        }
    }

    public TemplateOutputHandler(File templateFile, PrintData pd, PrintStream out) {
        super(pd, out);
        String[] t;
        InputStream in = null;
        try {
            if (!templateFile.isFile()) {
                throw new FileNotFoundException("No such file: " + templateFile);
            }
            long length = templateFile.length();
            if (length > MAX_TEMPLATE_FILE_LENGTH) {
                System.err.println("Template file is too large. ");
                System.exit(7);
            }
            if (length == 0) {
                System.err.println("Template file is empty. ");
                System.exit(7);
            }
            byte[] buffer = new byte[(int) length + 1];
            in = new FileInputStream(templateFile);
            buffer[in.read(buffer)] = '#';
            t = new String(buffer).split(LS);
            t[t.length - 1] = t[t.length - 1].substring(0, t[t.length - 1].length() - 1);
            for (int i = 0; i < t.length; i++) {
                String s = t[i].trim();
                if (s.startsWith("##")) {
                    for (Scope ss : Scope.values()) {
                        if (ss.val.equals(s)) {
                            i = process(ss, ++i, t);
                            break;
                        }
                    }
                }
            }
            if (headTemplate.isBlank() && tailTemplate.isBlank() && file1Template.isBlank()
                    && file2Template.isBlank() && itemTemplate.isBlank()) {
                System.err.println("The template file do not have any template. ");
                System.exit(7);
            }
            if (
                    file1Template.isBlank() ||
                    (!file1Template.contains("%s") && !file1Template.contains("%d")) ||
                    itemTemplate.isBlank() ||
                    (!itemTemplate.contains("%s") && !itemTemplate.contains("%d"))
            ) {
                System.out.println("WARN: The template may be something goes wrong." + LS +
                                   "      Did you ignore to specify FILE HEAD scope or ITEM scope?");
            }
        } catch (FileNotFoundException e) {
            System.err.println("No such file: " + templateFile.getName());
            System.exit(7);
        } catch (IOException e) {
            System.err.println("Unable to read the template file: " + templateFile.getName());
            System.exit(7);
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException ignored) {}
            }
        }
    }


    private int process(Scope scope, int lineNumber0, String[] template) {
        if (scope.equals(Scope.END)) {
            System.err.format("(Line: %d) WARN: No begin label match \"%s\".%n",
                    lineNumber0 + 1, Scope.END.val);
            return lineNumber0;
        }

        if (scope.clear(this)) {
            System.err.format("(Line: %d) WARN: Met too many scope of \"%s\"%n", lineNumber0, scope.val);
        }

        StringBuilder sb = new StringBuilder();
        for (; lineNumber0 < template.length; lineNumber0++) {

            if (template[lineNumber0].trim().equals(Scope.END.val)) {
                scope.setTemplate(sb.toString(), this);
                return lineNumber0;
            }

            String[] ss = splitBy$(template[lineNumber0]);
            if (ss.length % 2 == 0) {
                System.err.format("(Line: %d) ERROR: Template parse error, " +
                        "unexpected number of '$': \"%s\"" + LS, lineNumber0 + 1, template[lineNumber0]);
                System.exit(7);
            }

            for (int i1 = 0; i1 < ss.length; i1++) {
                String si1 = ss[i1];
                String si1u = si1.toUpperCase(Locale.ROOT);

                if (i1 % 2 == 0) {
                    sb.append(si1.replaceAll("%", "%%"));

                } else {
                    String rep = VARIABLES.get(si1u);
                    if (rep == null) {
                        // TODO
                        /*if (si1u.startsWith("IF(") && si1u.endsWith(")")) {

                            String[] args = si1u.substring(3, si1u.length() - 1).split(",");
                            if (args.length < 2) {
                                System.err.format("(Line: %d) ERROR: IF condition missing " +
                                        "1 argument: \"%s\"" + LS, lineNumber0 + 1, si1);
                                System.exit(7);
                            }
                            if (args.length > 3) {
                                System.err.format("(Line: %d) ERROR: IF condition got too many " +
                                        "arguments: \"%s\"" + LS, lineNumber0 + 1, si1);
                                System.exit(7);
                            }
                            rep = "%s";

                        } else {*/
                        System.err.format("(Line: %d) WARN: Unrecognized template " +
                                "variable: \"%s\"" + LS, lineNumber0 + 1, si1);
                        rep = si1.replaceAll("%", "%%");
                        /*}*/
                    } else {
                        scope.append(si1u, this);
                    }
                    sb.append(rep);
                }
            }
            sb.append(LS);
        }
        System.err.format("(Line: %d) WARN: No end label match \"%s\".%n",
                lineNumber0, scope.val);
        scope.setTemplate(sb.toString(), this);
        return lineNumber0;
    }

    private String[] splitBy$(String line) {
        char c = 0;
        int beginInd = 0, endInd = 0;
        char[] chars = line.toCharArray();
        List<String> sList = new ArrayList<>();
        for (char cc : chars) {
            if (cc == '$') {
                if (c != '\\') {
                    sList.add(line.substring(beginInd, endInd).replaceAll("\\\\\\$", "\\$"));
                    beginInd = endInd + 1;
                }
            }
            endInd++;
            c = cc;
        }
        sList.add(line.substring(beginInd, endInd));
        return sList.toArray(new String[0]);
    }

    private void format(String fmt, List<Field> fields) {
        out.format(fmt, fields.stream().map(f -> {
            try {
                return f.get(data);
            } catch (IllegalAccessException ignored) {
                return null;
            }
        }).toArray());
    }

    @Override
    public void printHead() {
        format(headTemplate, HEAD);
    }

    @Override
    public void printTail() {
        format(tailTemplate, TAIL);
    }

    @Override
    public void printFileHead() {
        format(file1Template, FILE_HEAD);
    }

    @Override
    public void printFileTail() {
        format(file2Template, FILE_TAIL);
    }

    @Override
    public void printItem() {
        format(itemTemplate, ITEM);
    }

    @Override
    public void close() {
        out.close();
    }

}
